/*
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.zanata.webtrans.client.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.customware.gwt.presenter.client.EventBus;

import org.zanata.webtrans.client.events.NotificationEvent;
import org.zanata.webtrans.client.events.NotificationEvent.Severity;
import org.zanata.webtrans.client.events.RequestValidationEvent;
import org.zanata.webtrans.client.events.RunValidationEvent;
import org.zanata.webtrans.client.events.RunValidationEventHandler;
import org.zanata.webtrans.client.presenter.UserConfigHolder;
import org.zanata.webtrans.client.resources.ValidationMessages;
import org.zanata.webtrans.client.ui.HasUpdateValidationMessage;
import org.zanata.webtrans.shared.model.ValidationAction;
import org.zanata.webtrans.shared.model.ValidationAction.State;
import org.zanata.webtrans.shared.model.ValidationId;
import org.zanata.webtrans.shared.validation.ValidationFactory;

import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 *
 * @author Alex Eng <a href="mailto:aeng@redhat.com">aeng@redhat.com</a>
 *
 **/

@Singleton
public class ValidationService implements RunValidationEventHandler {
    private final EventBus eventBus;
    private final ValidationMessages messages;
    private final ValidationFactory validationFactory;
    private final UserConfigHolder configHolder;
    private Map<ValidationId, ValidationAction> validationMap = Maps
            .newHashMap();

    @Inject
    public ValidationService(final EventBus eventBus,
            final ValidationMessages validationMessages,
            final UserConfigHolder configHolder) {
        this.eventBus = eventBus;
        this.messages = validationMessages;
        this.configHolder = configHolder;

        validationFactory = new ValidationFactory(messages);

        eventBus.addHandler(RunValidationEvent.getType(), this);
    }

    @Override
    public void onValidate(RunValidationEvent event) {
        execute(event.getSourceContent(), event.getTarget(),
                event.isFireNotification(), event.getWidgetList());
    }

    /**
     * Run all enabled validators against the given strings. Generates a
     * {@link HasUpdateValidationMessage}, which will be empty if no warnings
     * were generated by the enabled validators.
     */
    public void execute(String source, String target, boolean fireNotification,
            ArrayList<HasUpdateValidationMessage> widgetList) {
        Map<ValidationAction, List<String>> errors = Maps.newHashMap();

        for (ValidationAction validationAction : validationMap.values()) {
            if (validationAction.getRules().isEnabled()) {
                List<String> errorMessage =
                        validationAction.validate(source, target);
                if (!errorMessage.isEmpty()) {
                    errors.put(validationAction, errorMessage);
                }
            }
        }
        updateValidationMessage(errors, fireNotification, widgetList);
    }

    /**
     * Enable/disable validation action. Causes a {@link RequestValidationEvent}
     * to be fired.
     *
     * @param key
     * @param isEnabled
     * @param fireRequestValidationEvent
     */
    public void updateStatus(ValidationId key, boolean isEnabled,
            boolean fireRequestValidationEvent) {
        ValidationAction action = validationMap.get(key);
        action.getRules().setEnabled(isEnabled);

        updateConfigHolder();

        if (fireRequestValidationEvent) {
            // request re-run validation with new options
            eventBus.fireEvent(RequestValidationEvent.EVENT);
        }
    }

    public Map<ValidationId, ValidationAction> getValidationMap() {
        return validationMap;
    }

    public void updateValidationMessage(
            Map<ValidationAction, List<String>> errors,
            boolean fireNotification,
            ArrayList<HasUpdateValidationMessage> widgetList) {
        if (!errors.isEmpty() && fireNotification) {
            eventBus.fireEvent(new NotificationEvent(Severity.Info, messages
                    .notifyValidationError()));
        }

        if (widgetList != null) {
            for (HasUpdateValidationMessage widget : widgetList) {
                widget.updateValidationMessages(errors);
            }
        }
    }

    /**
     * Update state of validationAction from RPC result to all validation
     * actions from ValidationFactory
     *
     * @param validationStates
     */
    public void setValidationRules(Map<ValidationId, State> validationStates) {
        validationMap.clear();
        for (Map.Entry<ValidationId, State> entry : validationStates.entrySet()) {
            if (!validationMap.containsKey(entry.getKey())) {
                ValidationAction action =
                        validationFactory.getValidationAction(entry.getKey());
                action.setState(entry.getValue());
                validationMap.put(entry.getKey(), action);

                // if this rule is locked, then lock it's mutual exclusive rule
                // as well.
                if (action.getRules().isLocked()) {
                    for (ValidationAction exclusiveAction : action
                            .getExclusiveValidations()) {
                        exclusiveAction.getRules().setEnabled(false);
                        exclusiveAction.getRules().setLocked(true);
                        validationMap.put(exclusiveAction.getId(),
                                exclusiveAction);
                    }
                }
            }
        }
        updateConfigHolder();
    }

    private void updateConfigHolder() {
        ArrayList<ValidationId> enabledValidations =
                new ArrayList<ValidationId>();
        for (ValidationAction valAction : getValidationMap().values()) {
            if (valAction.getRules().isEnabled()) {
                enabledValidations.add(valAction.getId());
            }
        }
        configHolder.setEnabledValidationIds(enabledValidations);
    }
}
